#include  "usbd_app.h"
#include  "usbd_ioreq.h"
#include  "usbd_comp_if.h"
#include  "usbd_conf.h"
#include "system_property.h"

struct __usbd_app_data {
	uint8_t is_initialized;
	osMessageQId work_queue;
	osThreadId work_thread;
};

static struct __usbd_app_data g_usbd_app_data = { 0 };

typedef struct usb_applet
{
    uint8_t                usb_app_id;
    Notify_DataCome_func_t usb_app_rcv_data;
    struct usb_applet      *usb_app_next;
}USB_Applet_TypeDef_t;

#define USB_APP_LIST_LOCKED                (0)
#define USB_APP_LIST_UNLOCKED              (1)

USB_Applet_TypeDef_t *usb_app_mang_list_head = NULL;
uint8_t USB_APP_Rcv_Buffer[COMP_CDC_DATA_FS_MAX_PACKET_SIZE] = {'\0'};
static uint8_t usb_app_list_lock = USB_APP_LIST_UNLOCKED;
static uint8_t usb_app_rcv_data_len = 0;
static uint8_t usb_app_data_come_flag = 0;
int8_t UnRegister_All_USB_APP(void);


static void init_usb_app_list_lock(uint8_t lock)
{
    usb_app_list_lock = lock;
}
static void usb_app_lock_list(void)
{
    do
    {
        if(usb_app_list_lock == USB_APP_LIST_UNLOCKED)
        {
            usb_app_list_lock = USB_APP_LIST_LOCKED;
            break;
        }
        else if(usb_app_list_lock == USB_APP_LIST_LOCKED)
        {
            osDelay(10);
        }
    }while(1);
}

static void usb_app_unlock_list(void)
{
    do
    {
        if(usb_app_list_lock != USB_APP_LIST_UNLOCKED)
        {
            usb_app_list_lock = USB_APP_LIST_UNLOCKED;
        }
    }while(0);
}

int32_t APP2_ProcessRcvData(uint8_t * buf, uint8_t len);
extern int32_t fota_ProcessRcvData(uint8_t * buf, uint8_t len);
extern int32_t BMI160_ACCEL_K6(uint8_t * buf, uint8_t len);
int32_t usb_bootmode(uint8_t * buf, uint8_t len);
extern int32_t PWRMGR_ProcessRcvData(uint8_t * buf, uint8_t len);
extern unsigned int g_MFGModeFlag;

uint8_t usb_app_init (void)
{
    init_usb_app_list_lock(USB_APP_LIST_UNLOCKED);
    usb_app_mang_list_head = NULL;
    usb_app_rcv_data_len = 0;
    usb_app_data_come_flag = 0;
    USBD_memset(USB_APP_Rcv_Buffer, 0x00, COMP_CDC_DATA_FS_MAX_PACKET_SIZE);
    
    Register_USB_APP(USB_APPID_1, fota_ProcessRcvData);
	if(g_MFGModeFlag == 1)
	{
		Register_USB_APP(USB_APPID_4, BMI160_ACCEL_K6);
	}
    Register_USB_APP(USB_APPID_5, system_property);
	Register_USB_APP(USB_APPID_POWER, PWRMGR_ProcessRcvData);
/*********************for Debug start*************************/
    Register_USB_APP(USB_APPID_2, APP2_ProcessRcvData);
/*********************for Debug start*************************/
    Register_USB_APP(USB_APPID_6,usb_bootmode);
    return 0;
}

uint8_t usb_app_deinit (void)
{
    UnRegister_All_USB_APP();
    return 0;
}

extern USBD_HandleTypeDef  *hUsbDevice_0;
uint32_t usb_app_get_tx_status(void)
{
    USBD_COMP_HandleTypeDef *hcomp = NULL;
    hcomp = (USBD_COMP_HandleTypeDef*)hUsbDevice_0->pClassData;
    return hcomp->TxState;
}

int8_t usb_app_transmit_data(uint8_t *buf, uint8_t len)
{
    uint8_t ret = -1;
    uint8_t transmit_data_fail_retry_counter = 0;
    uint32_t cur_tx_status;
    if (buf == NULL || len == 0) {
       return -1;
    }
    if (len > USB_MAX_TX_BUF_SIZE) {
       len = USB_MAX_TX_BUF_SIZE;
    }
    
    do{
        cur_tx_status = usb_app_get_tx_status();
        if( cur_tx_status == USB_TX_IDLE)
        {
            ret = COMP_CDC_Transmit_FS(buf,len);
            break;
        }
        else
        {
            ++transmit_data_fail_retry_counter;
        }
        
        osDelay(100);
        
      }while(transmit_data_fail_retry_counter < 3);
        
    return ret;
}

int32_t APP2_ProcessRcvData(uint8_t * buf, uint8_t len)
{
    if(buf == NULL)
    {
        return -1;
    }
    uint8_t      temp_buf[COMP_CDC_DATA_FS_MAX_PACKET_SIZE];
    USBD_memset(temp_buf, 0x00, COMP_CDC_DATA_FS_MAX_PACKET_SIZE);
    uint8_t app_len = strlen("APP2--> ");
    USBD_memcpy(temp_buf, "APP2--> ", app_len);
    if(len + app_len >= USB_FS_MAX_PACKET_SIZE)
    {
        uint8_t overflow_len = len + app_len - COMP_CDC_DATA_FS_MAX_PACKET_SIZE;
        USBD_memcpy(temp_buf+app_len, buf, len - overflow_len);
        usb_app_transmit_data(temp_buf,len + app_len - overflow_len);
    }
    else
    {
        USBD_memcpy(temp_buf+app_len, buf, len);
        usb_app_transmit_data(temp_buf,len + app_len);
    }
    
    return 0;
}

static int8_t usb_app_is_already_register(USB_APPID_TypeDef_t appid)
{
    int8_t ret = 0;
    USB_Applet_TypeDef_t *temp_retrieve = NULL;
    if(usb_app_mang_list_head == NULL)
    {
        return -1;
    }
    
    for(temp_retrieve = usb_app_mang_list_head;temp_retrieve != NULL;temp_retrieve = temp_retrieve->usb_app_next)
    {
        if(temp_retrieve->usb_app_id == appid)
        {
            ret = 1;
            break;
        }
    }

    return ret;
}
int8_t Register_USB_APP(USB_APPID_TypeDef_t appid, Notify_DataCome_func_t func)
{
    USB_Applet_TypeDef_t *new_app = NULL;
    USB_Applet_TypeDef_t *temp_retrieve = NULL;
    int8_t ret = 0;
   
    usb_app_lock_list();
    ret = usb_app_is_already_register(appid);
    if(ret == 1)
    {
        usb_app_unlock_list();
        
        return -1;
    }

    new_app = (USB_Applet_TypeDef_t *)USBD_malloc(sizeof(USB_Applet_TypeDef_t));
    if(new_app != NULL)
    {
        new_app->usb_app_id = appid;
        new_app->usb_app_rcv_data = func;
        new_app->usb_app_next = NULL;
    }
    else
    {
        usb_app_unlock_list();
        return -1;
    }

    
    if(ret == -1)
    {
        usb_app_mang_list_head = new_app;
        usb_app_unlock_list();
        return 0; 
    }
    
    for(temp_retrieve = usb_app_mang_list_head;temp_retrieve != NULL; temp_retrieve = temp_retrieve->usb_app_next)
    {
        if(temp_retrieve->usb_app_next == NULL)
        {
            temp_retrieve->usb_app_next = new_app;
            break;
        }
    }
    
    usb_app_unlock_list();

    return 0;
}


void usb_app_free(void *arg)
{
    if(arg != NULL)
    {
        USBD_free(arg);
        arg = NULL;
    }
}

int8_t UnRegister_One_USB_APP(USB_APPID_TypeDef_t app_id)
{
    usb_app_lock_list();
    
    if(usb_app_is_already_register(app_id) == 0)
    {
        usb_app_unlock_list();
        return -1;
    }
    
    USB_Applet_TypeDef_t *temp_retrieve = NULL;
    USB_Applet_TypeDef_t *temp_free = NULL;
    
    for(temp_retrieve = usb_app_mang_list_head;temp_retrieve != NULL;temp_retrieve = temp_retrieve->usb_app_next)
    {        
        if(usb_app_mang_list_head->usb_app_id == app_id)
        {
            temp_free = usb_app_mang_list_head;
            usb_app_mang_list_head = temp_free->usb_app_next;
            
            temp_free->usb_app_id = USB_APPID_INVAILD;
            temp_free->usb_app_rcv_data = NULL;
            temp_free->usb_app_next = NULL;
            usb_app_free(temp_free);
            break;
        }
    
        if(temp_retrieve->usb_app_next->usb_app_id == app_id)
        {
            temp_free = temp_retrieve->usb_app_next;
            temp_retrieve->usb_app_next = temp_free->usb_app_next;
            
            temp_free->usb_app_id = USB_APPID_INVAILD;
            temp_free->usb_app_rcv_data = NULL;
            temp_free->usb_app_next = NULL;
            usb_app_free(temp_free);   
            break;
        }
    }
    
    usb_app_unlock_list();
    
    return 0;
}

int8_t UnRegister_All_USB_APP(void)
{
    USB_Applet_TypeDef_t *temp_retrieve = NULL;
    USB_Applet_TypeDef_t *temp_free = NULL;

    
    usb_app_lock_list();
    
    for(temp_retrieve = usb_app_mang_list_head;temp_retrieve != NULL;)
    {    
        if(usb_app_mang_list_head == temp_retrieve)
        {
            temp_free = usb_app_mang_list_head;
            temp_retrieve = temp_free->usb_app_next;
            usb_app_mang_list_head = temp_free->usb_app_next;
            usb_app_free(temp_free);
        }    
    }
    
    usb_app_unlock_list();
    
    return 0;
}

static uint8_t usbd_app_get_data_come_flag(void)
{
    return usb_app_data_come_flag;
}

void usbd_app_set_data_come_flag(uint8_t flag_value)
{
    struct __usbd_app_data *usbd_app_data = &g_usbd_app_data;
    usb_app_data_come_flag = flag_value;
    if (usbd_app_data->is_initialized != 0 && flag_value != 0) {
    	osSignalSet(usbd_app_data->work_thread, 1);
    }
}

void usbd_app_set_rcv_data_len(uint8_t len)
{
    usb_app_rcv_data_len = len;
}

static uint8_t usbd_app_get_rcv_data_len(void)
{
    return usb_app_rcv_data_len;
}

void usbd_app_handle_command(void)
{
    USB_Applet_TypeDef_t *temp_retrieve;
    if(usbd_app_get_data_come_flag() == 1)
    {
        usb_app_lock_list();
        for(temp_retrieve = usb_app_mang_list_head;;temp_retrieve = temp_retrieve->usb_app_next)
        {
            if(temp_retrieve ==  NULL )
            {
                break;
            }
            
            if(temp_retrieve->usb_app_id == USB_APP_Rcv_Buffer[0])
            {
                temp_retrieve->usb_app_rcv_data(USB_APP_Rcv_Buffer, usbd_app_get_rcv_data_len());
                break;
            }
            
            if(temp_retrieve->usb_app_next == NULL)
            {
                //for debug only
                usb_app_transmit_data(USB_APP_Rcv_Buffer,usbd_app_get_rcv_data_len());
                break;
            }                  
        }
        usb_app_unlock_list();

        usbd_app_set_data_come_flag(0);
    }
}

void __usbd_app_handle_command_task(void const * argument)
{
	do {
		osSignalWait(0xFFFFFFFF, osWaitForever);
		usbd_app_handle_command();
	} while (1);
}

static int __create_usbd_app_work_task(
			struct __usbd_app_data *usbd_data)
{
	osThreadDef(usbd_app_task, __usbd_app_handle_command_task,
			osPriorityNormal, 0, configMINIMAL_STACK_SIZE * 2);
	usbd_data->work_thread =
		osThreadCreate(osThread(usbd_app_task), usbd_data);
	if (usbd_data->work_thread == NULL)
		return -1;

	return 0;
}

int usbd_app_init(void)
{
	struct __usbd_app_data *usbd_app_data = &g_usbd_app_data;
	if (usbd_app_data->is_initialized) {
		htc_usbd_app_info("%s: already initialized, exit\n", __func__);
		return 0;
	}

	if (__create_usbd_app_work_task(usbd_app_data) != 0)
		htc_usbd_app_warning("%s: create work task failed\n",
								__func__);

	usbd_app_data->is_initialized = 1;
	return 0;
}
